package pl.mw.cmd.internal.telnetserver;

import java.net.InetSocketAddress;
import java.util.concurrent.Executor;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.ChannelGroupFuture;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.execution.ExecutionHandler;
import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;

import pl.mw.cmd.telnetserver.ReplaySender;

/**
 * Klasa bazowa dla serwera telnet.
 */
public abstract class BaseTelnetServer extends SimpleChannelUpstreamHandler {
	
	/** Otwarte kanaly. */
	private ChannelGroup openChannels;
	
	/** Fabryka kanalow */
	private ChannelFactory channelFactory;

	/** Handler wykonania z pula watkow. */
	private ExecutionHandler executionHandler;
	
	/**
	 * Startuje serwer telnet.
	 */
	public void start() {
		
		this.openChannels = new DefaultChannelGroup();
		Executor executor = new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576);
		this.channelFactory = new NioServerSocketChannelFactory(executor, executor);
		this.executionHandler = new ExecutionHandler(executor);

		ServerBootstrap bootstrap = new ServerBootstrap(this.channelFactory);
		bootstrap.setPipelineFactory(new TelnetServerPipelineFactory(this, executionHandler));
		Channel channel = bootstrap.bind(new InetSocketAddress(23));
		this.openChannels.add(channel);
	}
	
	/**
	 * Zatrzymuje serwer telnet.
	 */
	public void stop() {
		ChannelGroupFuture future = this.openChannels.close();
		future.awaitUninterruptibly();
		this.channelFactory.releaseExternalResources();
		this.executionHandler.releaseExternalResources();
	}
	
	/**
	 * {@inheritDoc}
	 * 
	 * @see SimpleChannelUpstreamHandler#channelConnected(ChannelHandlerContext,
	 *      ChannelStateEvent)
	 */
	@Override
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
		ReplaySender replaySender = new ReplaySenderImpl(ctx.getChannel());
		ctx.setAttachment(new ReplaySenderImpl(ctx.getChannel()));
		channelConnected(replaySender);
	}
	
	/**
	 * Informuje o nawiazaniu nowego polaczenia.
	 * @param channel polaczenie
	 */
	protected abstract void channelConnected(ReplaySender replaySender);
	
	/**
	 * {@inheritDoc}
	 * 
	 * @see SimpleChannelUpstreamHandler#messageReceived(ChannelHandlerContext,
	 *      MessageEvent)
	 */
	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
		String message = (String) e.getMessage();
		ReplaySender replaySender = (ReplaySender) ctx.getAttachment();
		messageReceived(message, replaySender);
	}
	
	/**
	 * Odbiera komunikat.
	 * @param message komunikat
	 * @param channel polaczenie
	 */
	protected abstract void messageReceived(String message, ReplaySender replaySender);
	
	/**
	 * {@inheritDoc}
	 * 
	 * @see SimpleChannelUpstreamHandler#channelOpen(ChannelHandlerContext, ChannelStateEvent)
	 */
	@Override
	public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {
		this.openChannels.add(e.getChannel());
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see SimpleChannelUpstreamHandler#exceptionCaught(ChannelHandlerContext,
	 *      ExceptionEvent)
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
		e.getCause().printStackTrace(System.out);
		e.getChannel().close();
	}
	
	/**
	 * Odpowiada za wysylanie odpowiedzi.
	 */
	private static class ReplaySenderImpl implements ReplaySender {

		/**
		 * Polaczenie.
		 */
		private Channel channel;
		
		/**
		 * Tworzy obiekt zwiazany ze wskazanym polaczeniem.
		 * @param channel polaczenie
		 */
		public ReplaySenderImpl(Channel channel) {
			this.channel = channel;
		}
		
		/**
		 * {@inheritDoc}
		 * 
		 * @see pl.mw.cmd.telnetserver.ReplaySender#send(java.lang.String)
		 */
		@Override
		public void send(String replayMessage) {
			ChannelFuture channelFuture = this.channel.write(replayMessage);
			try {
				channelFuture.await();
			} catch (InterruptedException e) {
				throw new IllegalStateException(e);
			}
		}

		/**
		 * {@inheritDoc}
		 * 
		 * @see pl.mw.cmd.telnetserver.ReplaySender#close()
		 */
		@Override
		public void close() {
			ChannelFuture channelFuture = this.channel.close();
			try {
				channelFuture.await();
			} catch (InterruptedException e) {
				throw new IllegalStateException(e);
			}
		}
	}
}
